
#include <QtCore/QCoreApplication>
#include <QRegExp>
#include <qregexp.h>
#include <QString>
#include <qstring.h>


#include "../NTReco/volumereader.h"
#include "../NTReco/CAttribute.h"
#include "../NTReco/CMFT.h"
#include "../NTReco/Attrs.h"

#include <iostream>
using namespace std;
#include <cstdio>
#include "../NTReco/utils.h"
#include "../NTReco/stdafx.h"
#include "../NTReco/MemoryBuffer.h"
#include <afxcom_.h>

#define EXPORT __declspec(dllexport)

EXPORT struct TIMEACTION {
	SYSTEMTIME t;
	QString action;//< of form: SIM.SIE.
	TIMEACTION(SYSTEMTIME t,QString action) {
		this->t=t;
		this->action=action;
	}

	static TIMEACTION fromTimes( NTFSFileTime* times ) 
	{
		FileNameTime& FN=times->fileNameTime;
		FileNameTime& SI = times->stdInfoTime;
		QString act="";
		SYSTEMTIME late = SI.modify;
		late = max(late,SI.access);
		late = max(late,SI.creation);
		late = max(late,SI.entry);
		late = max(late,FN.modify);
		late = max(late,FN.access);
		late = max(late,FN.creation);
		late = max(late,FN.entry);
		if (late==SI.modify)
			act+="SIM.";
		if (late==SI.access)
			act+="SIA.";
		if (late==SI.creation)
			act+="SIC.";
		if (late==SI.entry)
			act+="SIE.";
		if (late==FN.modify)
			act+="FNM.";
		if (late==FN.access)
			act+="FNA.";
		if (late==FN.creation)
			act+="FNC.";
		if (late==FN.entry)
			act+="FNE.";
		return TIMEACTION(late,act);
	}


};

EXPORT  void usage( int argc, char * argv[] ) 
{
	fprintf(stderr,"usage:\n"
		"\t %s [-tln] [-d <days>] [-header] diskfile\n",argv[0]);
	fprintf(stderr,	"\t -tln : output in TLN format\n"
		"\t -d <days> : only output event in recent <days> days"
		"\t -header : output csv header\n"
		"\t if you want to read disk directly , diskfile should be like \\\\.\\c: for disk C (and so on) .\n");
	exit(1);
}


// 
// QString ToCSVString( SYSTEMTIME & systime ) 
// {
// 	QString fmt=QString("%1,%2,%3,%4,%5,%6,%7,%8").arg(systime.wYear).arg(systime.wMonth).arg(systime.wDay).arg(systime.wDayOfWeek).
// 		arg(systime.wHour).arg(systime.wMinute).arg(systime.wSecond).arg(systime.wMilliseconds);
// 	return fmt;
// }

EXPORT  TIMEACTION TimeAction( SYSTEMTIME &t,QString action) 
{
	return TIMEACTION(t,action);
	//return action+"@"+"("+ToString(&t)+")";
}
//************************************
// Method:    InferenceInfo
// FullName:  InferenceInfo
// Access:    public 
// Returns:   QT_NAMESPACE::QString
// Qualifier:
// Parameter: NTFSFileTime t
// Parameter: const QString & ext
// Parameter: bool isDir
// TODO: when lastaccess is set to on in registry
//************************************

EXPORT  QList<TIMEACTION> InferenceInfo( NTFSFileTime t ,const QString& ext, bool isDir,CMFT& cur) 
{ 
	TIMEACTION ta = TIMEACTION::fromTimes(&t);
	StdInfoTime & SI = t.stdInfoTime;
	FileNameTime & FN = t.fileNameTime;
	QList<TIMEACTION> ret;
	QString readable="";//human readable action name
	//
	//big table inference
	if (isDir)
		goto L_DIR;
	//![1.new]
	if (AllEquel(SI)&&AllEquel(FN) && FN.creation==SI.creation) {
		//ret<<TimeAction(SI.creation,"create("+ta.action+")");
		readable+="create|";
	}
	//![new]
	//![2.rename|3.IntraMove]
	//:new SI.E and SI.CM==FN.CM a
	if (SI.creation==FN.creation&&SI.modify==FN.modify&&SI.access==FN.access && SI.entry>SI.access && 
		SI.entry>FN.modify&&SI.entry>FN.access&&SI.entry>FN.creation && SI.entry>FN.entry) 
		//ret<<TimeAction(SI.entry,"rename|intramove");
		readable+="rename|intramove|";
	//![ rename|intramove]
	//![4.interMove] 
	if (AllEquel(FN)&& SI.access==FN.access &&SI.entry==FN.entry &&SI.creation<FN.creation && SI.modify <FN.modify) {
		//ret<<TimeAction(SI.access,"intermove");
		readable+="intermove|";
	}
	//![ 4.interMove]
	//![5 Copy]
	if (AllEquel(FN)&&SI.entry==FN.entry && SI.access==FN.access &&	SI.creation ==FN.creation && FN.modify > SI.modify) 
		//ret<<TimeAction(SI.access,"copy");
		readable+="copy|";
	//![ 5 copy]
	//![6 Modify content]
	if (SI.modify==SI.entry && SI.entry>SI.creation && SI.entry>SI.access &&
		SI.entry>FN.modify&&SI.entry>FN.access&&SI.entry>FN.creation&&SI.entry>FN.entry) 
		//ret<<TimeAction(SI.modify,"modifyContent");
		readable+="modifyContent|";
	if (ext=="doc") {//office file
		if (SI.modify==SI.entry && SI.entry==SI.access&& SI.entry>SI.creation &&
			FN.modify==FN.entry && FN.entry==FN.access&& FN.entry>FN.creation && FN.modify==SI.modify)
			//ret<<TimeAction(SI.modify,"modifyOfficeContent");
			readable+="modifyByOffice|";
	}
	//![ 6 Modify content]
	//![7 modify general attribute]
	//:si.e is new
	if (ta.action=="SIE.") {
		//ret<<TimeAction(SI.entry,"ModifyGeneralAttr|IntraReplace|");
		readable+="ModifyGeneralAttr|IntraReplace|";
	}
	//:SI.E is new
	//: 
	//![7]
	//![8 modify summary attr. actually modify content ,so deleted]
	//![ 8]
	//![9 intra-replace]
	//combined to 7.modify genrl attr
	//![9]
	//![10 inter replace]
	//TODO:inter replace which may need cross reference
	//![10]
	//![ 11 touch] 
	//TODO:touch
	//![ 11]
	//[12 open]
	//TODO:open
	//![open]
	//[extra: backup by robocopy]
	if (ta.action=="SIE.FNM.FNA.FNC.FNE.") 
		readable+="Backup|";
	//[extra: copy by ftkImager]
	if (ta.action=="FNM.FNA.FNC.FNE.")
		readable+="CopyByFTKImager|";
	//[extra: modify content ]
	if (ta.action=="SIM.SIA.SIE.FNM.FNA.FNE.")
		readable+="modifyContent|";
	if (ta.action=="SIA.SIC.FNM.FNA.FNC.FNE.")
		readable+="modifyContent|";
	//[extra: copy]
	if (ta.action=="SIA.SIC.SIE.FNA.FNC.FNE.")
		readable+="copy|";
	//[extra: interMove]
	if (ta.action=="SIA.SIC.SIE.FNA.FNE.") 
		readable+="interMove|";
	goto L_DIREND;
L_DIR:
	//![1.new 5.copy]
	if (AllEquel(FN)&&AllEquel(SI)&&FN.entry==SI.entry) {
		//ret<<TimeAction(SI.entry,"createdir|copy");
		readable+="createdir|copy|";
	}
	//![1]
	//![2.rename|3.IntraMove]
	//:new SI.E and FN inherit SI
	if (SI.creation==FN.creation&&SI.modify==FN.modify&&SI.access==FN.access && SI.entry>SI.access && 
		SI.entry>FN.modify&&SI.entry>FN.access&&SI.entry>FN.creation && SI.entry>FN.entry) 
		//ret<<TimeAction(SI.entry,"rename|intramove");
		readable+="rename|intramove|";
	//![ rename|intramove]
	//![ 4. inter move ]
	if (ta.action=="SIA.SIE.FNM.FNA.FNC.FNE." ||//contain no file(empty folder)
		ta.action=="SIM.SIA.SIE.FNM.FNA.FNC.FNE." //folders contain file
		)
		//ret<<TimeAction(SI.entry,"intermove");
		readable+="intermove|";
	//![ intermove]
	//![ 6.modify attr]
	//:new si.e
	if(SI.modify==SI.entry && SI.modify>SI.access && SI.modify>SI.creation &&
		SI.entry>FN.modify&&SI.entry>FN.access&&SI.entry>FN.creation&&SI.entry>FN.entry)
		//ret<<TimeAction(SI.modify,"ModifyAttr.");
		readable+="modifyAttr|";
	//![ modify attr
	//![7.replace 9.subDirChange]
	if (SI.modify==SI.entry&&SI.entry==SI.access && SI.modify>SI.creation &&
		SI.modify>FN.modify&&SI.modify>FN.access&&SI.modify>FN.creation&&SI.modify>FN.entry)
		//ret<<TimeAction(SI.modify,"replace|subDirChange");
		readable+="replace|subDirChange|";
	//![7]
	//NONE: [8 touch/access]
	//extra: SI.E
	if (ta.action=="SIE.") {
		readable+="subDirChange|";
	}
	//extra: subdirchange
	if (ta.action=="SIA.SIC.SIE.FNM.FNA.FNC.FNE.")
		readable+="subDirChange|";
L_DIREND:
	readable+="("+ta.action+")";
	ret<<TimeAction(ta.t,readable);
	return ret;

}

EXPORT void dumbib(int a ,bool b) {
	cout<<a<<b;
}

EXPORT void dumb() {
	cout<<"really dumb";
}

EXPORT void dumbi(int a) {
	cout<<a;
}
//calculate tamper 
QString InferTamper(  NTFSFileTime t,const QString& ext, bool isDir,CMFT& cur)
{
	QString ret;
	FileNameTime &fn = t.fileNameTime;
	StdInfoTime &si = t.stdInfoTime;
	TIMEACTION ta = TIMEACTION::fromTimes(&t);

	if (isDir) {
		if (si.modify>si.entry)
			ret+="SI.M>SI.E";
		if ((si.creation>fn.creation))
			ret+="SI.C>FN.C";
		if (si.creation>si.access) {
			ret+="SI.C>SI.A";
		}
	} else {
		if ( (ext=="dll"||ext=="exe") && ta.action=="SIM.SIE.") {//modified PE
			ret+="|modifiedPE";
		}
		if (ext=="doc" || ext=="docx") {//HACK: office file
			if (!(si.modify==fn.modify))
				ret+="SI.M!=FN.M";
		} else {//normal files
			if (fn.modify==fn.access && fn.access==fn.creation && fn.creation==fn.entry 
				|| fn.creation==fn.modify && fn.modify==fn.access && ta.action=="SIE.")
				;
			else ret+="FN.MACE_NonEqual";
			if (fn.creation<si.creation) {
				ret+="FN.C<SI.C";
			} else if (fn.creation>si.creation)
				ret+="FN.C>SI.C";
			if (si.entry<fn.entry) {
				ret+="SI.E<FN.E";
			}
		}
	}

	return ret;
}

struct Config  {
	QString imagePath;
	QDateTime sTime;
	QDateTime tTime;
	bool isTLN;
	bool header;
	bool hasTimeSpan;
	Config() {
		imagePath="";
		isTLN=false;
		header=false;
		hasTimeSpan = false;
	}
} CFG;

void setConfig(int argc,char* argv[]) {
#ifdef _DEBUG
	fprintf(stderr,"arguments are : \n");
	for(int i = 0;i<argc;i++)
		fprintf(stderr,"\t#%d:%s\n",i,argv[i]);
#endif
	for(int i = 1;i<argc;i++)
		if (strcmp("-tln",argv[i])==0)
			CFG.isTLN=true;
		else if (strcmp("-header",argv[i])==0)
			CFG.header=true;
		else if (strcmp("-d",argv[i])==0) {
			CFG.hasTimeSpan = true;

			int days = atoi(argv[(++i)]);
			CFG.sTime = QDateTime::currentDateTime().addDays(-days);
			CFG.tTime = QDateTime::currentDateTime();
		}
		else if (strncmp("-",argv[i],1)==0) {
			cerr<<"unexcepted option :"<<argv[i]<<endl;
			exit(-1);
		} else if (CFG.imagePath.isEmpty()) {
			CFG.imagePath=argv[i];
		}
		if (CFG.imagePath.isEmpty())
			usage(argc,argv);
}

EXPORT int main(int argc, char *argv[])
{
	QCoreApplication a(argc, argv);
	setConfig(argc,argv);
	QTextStream qout(stdout);
	VolumeReader *vr = new VolumeReader(CFG.imagePath);
	if (!vr->isReady()) {
		fprintf(stderr,"unable to open diskfile (%s), exiting...\n",CFG.imagePath.toStdString().c_str());
		exit(1);
	}
	QRegExp reVolName("\\\\\\\\\\.\\\\(\\w):");//match r"\\\\\.\\(\w):"
	QString volchar = 'C';
	int pos = reVolName.indexIn(CFG.imagePath);
	if (pos!=-1) {
		volchar = reVolName.cap(1);
	} else  {
		cerr<<"input a char to specify volume letter:";
		char ch;
		cin>> ch;
		volchar = ch;
	}
	volchar = volchar.toUpper();


	CMFT masterMFT = vr->MFTEntry(0);
	Q_ASSERT(masterMFT.countAttr(AttrType::Data)==1);
	//xprintMFTInfo(&masterMFT);
	//xstd::cout<<"input $DATA id:";
	int masterDataId;
	for (int i = 0;i<masterMFT.nresAttrs.size();i++)
		if (masterMFT.nresAttrs[i]->Header.TypeID==AttrType::Data)
			masterDataId = masterMFT.nresAttrs[i]->Header.ID;
	//xis>>masterDataId;
	CAttribute masterData = masterMFT.getAttribute(AttrType::Data,masterDataId);
	char buffer[1024];
	//scan all mft and store in mftMap
	UINT64 mftsize = masterData.size();
	//output header
	if (CFG.header)
		if (CFG.isTLN)
			qout<<"time ,source ,host ,user ,action ,target ,misc ,tampered\n";
		else 
			qout<<"mftno,filename,LSN,isdeleted,time,action,sim,sia,sic,sie,fnm,fna,fnc,fne,brokenTimes"<<endl;
	for (UINT64 i = 0*1024;i < masterData.size();i+=1024) {
		//progress
		if (i%(1024*100)==0) {
			std::cerr<<"progress "<<i/1024<<"/"<<mftsize/1024<<" "<<(double)i/mftsize*100<<"% hitRatio:"<<vr->m_buffer->getHitRatio()*100<<"%\r";
			vr->m_buffer->resetHitRatio();
		}

		//work

		UINT64 diskOffset = masterMFT.getAttPos(AttrType::Data,masterDataId,i);
		if(!vr->isMFT(diskOffset)) {
			std::cerr<<"Not MFT AT entry :"<<i/1024<<"\n";
			continue;
		}
		CMFT cur= vr->MFTAt(diskOffset);
		//printMFTInfo(&cur);
		if (!cur.hasExisted())//exclude mft that have never been used
			continue;
		if (!cur.isBase()) {
#ifdef _DEBUG
			std::cerr<<"NONE BASE MFT AT"<<i<<std::endl;
#endif 
			continue;
		}
		//if reach here then |cur| is a valid mft
		QString filename = cur.getFileName();
		QString fullname;
		if (!filename.isNull())
			fullname = cur.getPath()+filename;
		else fullname = "$$NONAME";
		if (cur.isDirectory())
			fullname.append('\\');	
		if (cur.isOrphan())
			fullname = ".\\$OrphanFile\\"+fullname;
		NTFSFileTime times;
		memset(&times,0,sizeof(times));
		if (cur.haveTimes())
			times = cur.getTimes();
		QList<TIMEACTION> inference=InferenceInfo(times,cur.getFileNameExtension(),cur.isDirectory(),cur);
		QString origName;
		//recycle bin inference
		if (fullname.startsWith(".\\$RECYCLE.BIN\\",Qt::CaseInsensitive) &&filename.startsWith("$I")) {
			//a recycle support file
			if (!cur.recycle) 
				continue;
			inference.push_back(TIMEACTION(FileTimeToSysTime(&(cur.recycle->deletetime)),"moveToRecycle"));
			origName=QString::fromWCharArray(cur.recycle->filename);
			//fullname = QString::fromWCharArray(cur.recycle->filename);
			//qout<<cur.entryNo()<<',';
			//qout<<QString("\"%1\",").arg(QString::fromWCharArray(cur.recycle->filename));
			//qout<<cur.getLSN()<<',';
			//qout<<cur.isDeleted()<<',';
			//qout<<ToString(&FileTimeToSysTime(&(cur.recycle->deletetime)))<<','<<"moveToRecycle"<<',';
			//qout<< (ToString(&times.stdInfoTime.modify))<<',';
			//qout<< (ToString(&times.stdInfoTime.access))<<',';
			//qout<< (ToString(&times.stdInfoTime.creation))<<',';
			//qout<< (ToString(&times.stdInfoTime.entry))<<',';
			//qout<<(ToString(&times.fileNameTime.modify))<<',';
			//qout<< (ToString(&times.fileNameTime.access))<<',';
			//qout<< (ToString(&times.fileNameTime.creation))<<',';
			//qout<< (ToString(&times.fileNameTime.entry));
			//qout<<endl;
			////continue;
		}
		if (inference.size()==0) {
			inference.push_back(TIMEACTION::fromTimes(&times));
		}
		//the format is 
		//mftno|filename|LSN|isdeleted|
		// time|action|times...
		QRegExp rxStartWord("^\\w");
		if (rxStartWord.indexIn(fullname)==-1)
			fullname = volchar+":"+fullname.mid(1); 
		SYSTEMTIME systimeStart = ToSYSTEMTIME(CFG.sTime);
		SYSTEMTIME systimeEnd = ToSYSTEMTIME(CFG.tTime);

		Q_FOREACH(TIMEACTION ta,inference) {
			if (CFG.hasTimeSpan)
				if (ta.t<systimeStart || ta.t>systimeEnd)
					continue;
			if (CFG.isTLN) { //time source host user action target misc tampered
				qout<<ToString(&ta.t)<<','//t
					<<"NTFS"<<','//src
					<<""<<","//hst
					<<""<<","//usr
					<<ta.action<<","//action
					<<"\""<<fullname<<"\","//target
					<<"lsn:"<<cur.getLSN()<<","//misc
					<<InferTamper(times,cur.getFileNameExtension(),cur.isDirectory(),cur)//tampered;
					<<endl;
			} else {
				qout<<cur.entryNo()<<',';
				qout<<QString("\"%1\",").arg(ta.action=="moveToRecycle"?origName:fullname);
				qout<<cur.getLSN()<<',';
				qout<<cur.isDeleted()<<',';//isdeleted
				qout<<ToString(&ta.t)<<','<<ta.action<<',';//time,action
				qout<< (ToString(&times.stdInfoTime.modify))<<',';//time stamps
				qout<< (ToString(&times.stdInfoTime.access))<<',';
				qout<< (ToString(&times.stdInfoTime.creation))<<',';
				qout<< (ToString(&times.stdInfoTime.entry))<<',';
				qout<<(ToString(&times.fileNameTime.modify))<<',';
				qout<< (ToString(&times.fileNameTime.access))<<',';
				qout<< (ToString(&times.fileNameTime.creation))<<',';
				qout<< (ToString(&times.fileNameTime.entry))<<",";
				qout<<InferTamper(times,cur.getFileNameExtension(),cur.isDirectory(),cur);
				qout<<endl;
			}
		}

	}
	delete vr;
}
